﻿using System.Collections.Generic;
using CodeGenerator.CSharp.Extensions;

namespace CodeGenerator.CSharp
{

    #region CodeUnit
    
    /// <summary>
    /// Base class for a unit of code (e.g., property, procedure, enum, file, etc.).
    /// </summary>
    public abstract class CodeUnit
    {

        #region Constructors

        protected CodeUnit() : this(string.Empty, CodeGenerator.CSharp.Modifiers.Private){}
        protected CodeUnit(string name) : this(name, CodeGenerator.CSharp.Modifiers.Private){}
        protected CodeUnit(string name, ModifierCollection modifiers) 
        {
            this.Name = name;
            this._Modifiers = modifiers;
        }

        #endregion

        #region Name
        public string Name { get; set; }
        #endregion

        #region Modifiers

            public ModifierCollection Modifiers { get { return this._Modifiers; } }
            private ModifierCollection _Modifiers;
        
        #endregion

        #region Documentation

        /// <summary>
        /// Identfies the documentation for this code unit, if any.
        /// </summary>
        public Documentation Documentation { get { return this._Documentation; } }
        private Documentation _Documentation = new Documentation();

        /// <summary>
        /// Appends any documentation this code unit has to the identified string builder.
        /// </summary>
        protected void AppendDocumentation(CodeBuilder Code)
        {
            if (this._Documentation.Count > 0)
                Code.Append(this.Documentation.ToString(this.IndentationLevel));
        }

        #endregion
   
        #region Attributes

        /// <summary>
        /// Identfies any attributes assocaited with this code unit.
        /// </summary>
        public AttributeCollection Attributes { get { return this._Attributes; } }
        private AttributeCollection _Attributes = new AttributeCollection();

        /// <summary>
        /// Appends any attributes this code unit has to the identified string builder.
        /// </summary>
        protected void AppendAttributes(CodeBuilder Code)
        {
            if (this._Attributes.Count > 0) 
                Code.Append(this.Attributes.ToString(this.IndentationLevel)); 
        }

        #endregion 

        #region Body

		/// <summary>
		/// Identfies the body of this code unit, if applicable.
		/// </summary>
		public Body Body
		{
			get
			{
				if (this._Body == null) this._Body = new Body(this);
				return this._Body;
			}
		}
		private Body _Body;

        #endregion

        #region CodeUnits

        /// <summary>
        /// Identfies any child code units of this code unit.
        /// </summary>
        public CodeUnitCollection CodeUnits
        {
            get
            {
                if (this._CodeUnits == null) this._CodeUnits = new CodeUnitCollection(this);
                return this._CodeUnits;
            }
        }
        private CodeUnitCollection _CodeUnits = null;

        #endregion

        #region Implements
        /// <summary>
        /// Identifies the interface member this code unit implmenents, if any.
        /// </summary>
        public string Implements { get; set; }
        public bool HasImplements()
        {
            if (this.Implements.IsSomething())
                return this.Implements.Length > 0;
            else
                return false;
        }
        //public string ImplementsAsString()
        //{
        //    if (Language.IsVB && this.HasImplements())
        //        return "Implements " + this.Implements;
        //    else
        //        return string.Empty;
        //}
 
        #endregion

        #region DataType

        public string DataType { get; set; }

        public bool HasDataType()
        {
            if (this.DataType.IsSomething())
                return this.DataType.Length > 0;
            else
                return false;
        }

        #endregion

        #region Parameters

        /// <summary>
        /// Identfies any parameters for this code unit.
        /// </summary>
        public ParameterCollection Parameters
        {
            get
            {
                if (this._Parameters == null) this._Parameters = new ParameterCollection();
                return this._Parameters;
            }
        }
        private ParameterCollection _Parameters = null;

        #endregion

        #region ToString

        /// <summary>
        /// Each derived class must override ToString that will return a string suitable for generating code.
        /// </summary>
        public abstract override string ToString();

        #endregion

        #region Indentation & Padding

        public int IndentationLevel 
		{
			get { return this._IndentationLevel; } 
			set 
			{
				this._IndentationLevel = value;
                //foreach (CodeUnit ThisCodeUnit in this.CodeUnits)
                //    ThisCodeUnit.IndentationLevel = this._IndentationLevel + 1;
			}
		}
        private int _IndentationLevel = 1;

        /// <summary>
        /// Identifies a string of the appropriate length (based on this code unit's IndentationLevel setting)
        /// used for padding when generating code.
        /// </summary>
        protected string Padding 
        { 
            get 
            { 
                return new string('\t', this._IndentationLevel); 
            } 
        }

        #endregion

        #region Add Code Units Shortcuts

        public ClassBuilder AddClass(string name, ModifierCollection modifiers)
        {
            ClassBuilder ThisCodeUnit = new ClassBuilder(name, modifiers);
            this.CodeUnits.Add(ThisCodeUnit);
            return ThisCodeUnit;
        }

        public void AddRegionAndClass(CodeGenerator.CSharp.ClassBuilder thisClass)
        {
            Region region = this.AddRegion(thisClass.Name);
            region.IndentationLevel = 1;
            //region.CodeUnits.BlankLinesBeforeCodeUnits = 1;
            region.CodeUnits.Add(thisClass);
            thisClass.IndentationLevel = 1;
        }

        public Namespace AddNamespace(string name)
        {
            Namespace ThisCodeUnit = new Namespace(name);
            this.CodeUnits.Add(ThisCodeUnit);
            return ThisCodeUnit;
        }

        public ConditionalCompilation AddConditionalCompilation(string name)
        {
            var ThisCodeUnit = new ConditionalCompilation(name);
            this.CodeUnits.Add(ThisCodeUnit);
            return ThisCodeUnit;
        }

        public ConditionalCompilation AddFullClrConditionalCompilation()
        {
            return this.AddConditionalCompilation("!SILVERLIGHT");
        }

        public Region AddRegion(string name)
        {
            Region ThisCodeUnit = new Region(name);
            this.CodeUnits.Add(ThisCodeUnit);
            return ThisCodeUnit;
        }
		public Region AddRegion_Constants() { return this.AddRegion("Constants"); }
		public Region AddRegion_Enumerations() { return this.AddRegion("Enumerations"); }
		public Region AddRegion_Constructors() { return this.AddRegion("Constructors"); }
		public Region AddRegion_Properties() { return this.AddRegion("Properties"); }
		public Region AddRegion_Methods() { return this.AddRegion("Methods"); }
		public Region AddRegion_Events() { return this.AddRegion("Events"); }
		public Region AddRegion_EventHandlers() { return this.AddRegion("Event Handlers"); }
		public Region AddRegion_SupportProcedures() { return this.AddRegion("Support Procedures"); }
		public Region AddRegion_DebuggerProxy() { return this.AddRegion("Debugger Proxy"); }


        public Procedure AddProcedure(string name, ModifierCollection modifiers) 
        { 
            return this.AddProcedure(name, modifiers, string.Empty); 
        }
        public Procedure AddProcedure(string name, ModifierCollection modifiers, string returnValueDataType)
        {
            Procedure ThisCodeUnit = new Procedure(name, modifiers, returnValueDataType);
            this.CodeUnits.Add(ThisCodeUnit);
            return ThisCodeUnit;
        }

        public Operator AddOperator(string name, ModifierCollection modifiers, string dataType)
        {
            Operator ThisCodeUnit = new Operator(name, modifiers, dataType);
            this.CodeUnits.Add(ThisCodeUnit);
            return ThisCodeUnit;
        }

        public Constructor AddConstructor(string name, ModifierCollection modifiers)
        {
            Constructor ThisCodeUnit = new Constructor(name, modifiers);
            this.CodeUnits.Add(ThisCodeUnit);
            return ThisCodeUnit;
        }

        public Field AddField(string name, ModifierCollection modifiers, string dataType)
        {
            Field ThisCodeUnit = new Field(name, modifiers, dataType);
            this.CodeUnits.Add(ThisCodeUnit);
            return ThisCodeUnit;
        }

        public Property AddProperty(string name, ModifierCollection modifiers, string dataType)
        {
            Property ThisCodeUnit = new Property(name, modifiers, dataType);
            this.CodeUnits.Add(ThisCodeUnit);
            return ThisCodeUnit;
        }

        public Property AddProperty(string name, ModifierCollection modifiers, string dataType, string fieldName)
        {
            Property ThisCodeUnit = new Property(name, modifiers, dataType, fieldName);
            this.CodeUnits.Add(ThisCodeUnit);
            return ThisCodeUnit;
        }

        public Event AddEvent(string name, ModifierCollection modifiers, string dataType)
        {
            Event ThisCodeUnit = new Event(name, modifiers, dataType);
            this.CodeUnits.Add(ThisCodeUnit);
            return ThisCodeUnit;
        }

		public Constant AddConstant(string name, string dataType, string value)
		{
			return this.AddConstant(name, CodeGenerator.CSharp.Modifiers.Private, dataType, value);
		}

        public Constant AddConstant(string name, ModifierCollection modifiers, string dataType, string value)
        {
            Constant ThisCodeUnit = new Constant(name, modifiers, dataType, value);
            this.CodeUnits.Add(ThisCodeUnit);
            return ThisCodeUnit;
        }
		
        #endregion

    }

    #endregion

    #region CodeUnitCollection

    /// <summary>
    /// Defines a collection of code units.
    /// </summary>
    public class CodeUnitCollection : List<CodeUnit>
    {
        #region Constructors

        public CodeUnitCollection(CodeUnit parent)
        {
            this._Parent = parent;
            this.BlankLinesBeforeCodeUnits = 1;
            this.BlankLinesBetweenCodeUnits = 1;
            this.BlankLinesAfterCodeUnits = 1; 
        }

        #endregion

        #region Add

        public new void Add(CodeUnit item)
        { 
            base.Add(item);

            if (this.Parent is Region)
                item.IndentationLevel = this.Parent.IndentationLevel;

            else if (this.Parent is ConditionalCompilation)
                item.IndentationLevel = this.Parent.IndentationLevel;

            else
                item.IndentationLevel = this.Parent.IndentationLevel + 1;
        }

        public void Add(CodeUnitCollection items)
        {
            foreach (var item in items)
                this.Add(item);
        }

        #endregion

        #region Parent
        
        public CodeUnit Parent { get { return this._Parent; } }
        private CodeUnit _Parent = null;

        #endregion

        #region Blank Lines

        /// <summary>
        /// Gets or sets the number of blank lines to insert before the code units of this 
        /// collection when writing out code.
        /// </summary>
        public int BlankLinesBeforeCodeUnits { get; set; }

        /// <summary>
        /// Gets or sets the number of blank lines to insert between the code units of this 
        /// collection when writing out code.
        /// </summary>
        public int BlankLinesBetweenCodeUnits { get; set; }

        /// <summary>
        /// Gets or sets the number of blank lines to insert after the code units of this 
        /// collection when writing out code.
        /// </summary>
        public int BlankLinesAfterCodeUnits { get; set; }

        #endregion

        #region ToString
        
        public override string ToString()
        {
            CodeBuilder Code = new CodeBuilder();

            //Padding before code units.
            for (int i = 0; i < this.BlankLinesBeforeCodeUnits; i++)
                Code.Add();

            //Add each of our code units to the output string.
            for (int i = 0; i < this.Count; i++)
            {
                Code.Append(this[i].ToString());

                //Padding between code units.  If we are on the last code unit, don't add anything.
                if (i < this.Count - 1)
                {
                    for (int j = 0; j < this.BlankLinesBetweenCodeUnits; j++)
                        Code.Add();
                }
            }
            
            //Clear any extra white space from the end of our string builder.
            Code = new CodeBuilder(Code.ToString().TrimEnd());

            //Padding after code units.
            for (int i = 0; i < this.BlankLinesAfterCodeUnits; i++)
                Code.Add();

            return Code.ToString();
        }

        #endregion
    }

    #endregion

}

